import importlib
import json
import shutil
import subprocess
import sys
import build_utils
import os

class BuildConfigParser:
    CONFIG_EXPORT_DIR = []
    PACKAGE_MODULE_PATH = []
    PACKAGE_CONFIG_PATH = []
    PACKAGE_PREFIX_PATH = []

    def __init__(self, build_mode, topdir, bindir, installdir, config_path, replace, args, toolchain, cxxflags, cflags, ldflags) -> None:
        self.build_mode = build_mode
        self.config_path = config_path
        self.topdir = topdir
        self.bindir = bindir
        self.installdir = installdir
        self.args = args
        self.toolchain = toolchain
        self.cxxflags = cxxflags
        self.cflags = cflags
        self.ldflags = ldflags
        self.defines=[]
        self.config = {}
        with open(self.config_path, 'r') as file:
            self.config = json.load(file, object_hook=lambda obj: {
                key: replace(value) for key, value in obj.items()
            })

    def exist_submodule(self, module_name):
        return self.config.get(module_name) is not None

    def config_environ(self, module_name, module, src_path, build_path):
        UPPER_NAME = module_name.replace('-','_').upper()
        include = ""
        libs = ""
        exports = []
        if "exports" in module:
            exports = module["exports"]

        install_path = f'{self.installdir}/{module_name}'
        if not build_utils.exist_substr_module(exports, f'{UPPER_NAME}_INCLUDE_DIR'):
            self.CONFIG_EXPORT_DIR.append(f'{UPPER_NAME}_INCLUDE_DIR={install_path}/include')

        if not build_utils.exist_substr_module(exports, f'{UPPER_NAME}_LIBS_DIR'):
            if os.path.exists(f'{install_path}/lib'):
                self.CONFIG_EXPORT_DIR.append(f'{UPPER_NAME}_LIBS_DIR={install_path}/lib')
            elif os.path.exists(f'{install_path}/lib64'):
                self.CONFIG_EXPORT_DIR.append(f'{UPPER_NAME}_LIBS_DIR={install_path}/lib64')
            else:
                self.CONFIG_EXPORT_DIR.append(f'{UPPER_NAME}_LIBS_DIR={install_path}')

        module_paths = build_utils.find_subdirs_with_suffix(f'{install_path}', ".cmake")

        if module_paths is not None:
            for module_path in module_paths:
                self.PACKAGE_MODULE_PATH.append(module_path[1])
                prefix = build_utils.remove_config_suffix(module_path[0])  # module_path[0].rstrip('-')
                self.CONFIG_EXPORT_DIR.append(f'{prefix}_DIR={module_path[1]}')
                self.CONFIG_EXPORT_DIR.append(build_utils.capital_literal(f'{prefix}_DIR={module_path[1]}'))
                self.PACKAGE_PREFIX_PATH.append(module_path[1])
                print(f'cmake_module_path: {prefix}_DIR={module_path[1]}')

        # self.PACKAGE_PREFIX_PATH.append(install_path)

        # prefix_paths = build_utils.find_subdirs_with_suffix(f'{install_path}', ".a")
        # if prefix_paths is not None:
        #     for prefix in prefix_paths:
        #         print(f'cmake_prefix_path: {prefix[0]}->{prefix[1]}')
        #         # self.PACKAGE_CONFIG_PATH.extend(prefix[1])
        #         self.PACKAGE_PREFIX_PATH.append(prefix[1])

        # prefix_paths = build_utils.find_subdirs_with_suffix(f'{install_path}', ".so")
        # if prefix_paths is not None:
        #     for prefix in prefix_paths:
        #         print(f'cmake_prefix_path: {prefix[0]}->{prefix[1]}')
        #         # self.PACKAGE_CONFIG_PATH.extend(prefix[1])
        #         self.PACKAGE_PREFIX_PATH.append(prefix[1])

    def export_build_environ(self, module):
        module_name = module["name"]
        self.config_environ(module_name, module, f'{self.topdir}/{path}', f'{self.bindir}/{path}')

    def build_submodule(self, module, exports):
        module_name = module["name"]
        config_info = self.config[module_name]
        path = config_info["path"]
        includes = []
        links = []
        deps = []
        options = []

        if build_utils.hash_compare(f'{self.topdir}/{path}', f'{self.bindir}/{path}', module) and not self.args.force_build:
            build_utils.message_success(f"{self.topdir}/{path} should not build again")
            self.config_environ(module_name, module, f'{self.topdir}/{path}', f'{self.bindir}/{path}')
            return
        build_utils.hash_write(f'{self.topdir}/{path}', f'{self.bindir}/{path}', False, module)
        if "include-dirs" in config_info:
            includes = config_info["include-dirs"]
        if "link-dirs" in config_info:
            links = config_info["link-dirs"]
        if "include" in module:
            includes.extend(module["include"])
        if "link" in module:
            links.extend(module["link"])
        if "deps" in config_info:
            deps = config_info["deps"]
        if "options" in config_info:
            options = config_info["options"]
        if "options" in module:
            options.extend(module["options"])
        if "cflags" in module:
            self.cflags +=' ' + module["cflags"]
        if "cxxflags" in module:
            self.cxxflags +=' ' + module["cxxflags"]
        if "ldflags" in module:
            self.ldflags +=' ' + module["ldflags"]
        if "defines" in module:
            self.defines.extend(module["defines"])
        options.extend(exports)
        build_utils.appemd_mention_message(options)

        pkg_config_path = ':'.join(self.PACKAGE_CONFIG_PATH)
        os.environ['PKG_CONFIG_PATH'] = pkg_config_path

        if includes is not None:
            for inc in includes:
                self.cxxflags += f" -I{inc} "
                self.cflags += f" -I{inc} "

        for df in self.defines:
            self.cflags += f' -D{df}'
            self.cxxflags += f' -D{df}'
        build_utils.print_trace_message("cmake flags", f'{self.cflags}')

        # cmake
        print(f'module:{module_name}, info: {config_info}')
        if config_info["build-type"] == "cmake":
            self.build_cmake(module_name,pkg_config_path, options, path, includes, links, deps)

        if not self.build_make(module_name, options, path, includes, links, deps):
            sys.exit(-1)

        self.build_install(module_name, path)

        build_utils.hash_write(f'{self.topdir}/{path}', f'{self.bindir}/{path}', True, module)
        self.config_environ(module_name, module, f'{self.topdir}/{path}', f'{self.bindir}/{path}')
        build_utils.clear_mention_message()

    def build_install(self, module_name, path):
        srcpath = f'{self.topdir}/{path}'
        binpath = f'{self.bindir}/{path}'
        installpath = f'{self.installdir}/{module_name}'
        if not self.has_patch_build(module_name, 'install'):
            return build_utils.exec_command(["make", "install", f'--directory={binpath}'], "make install", binpath, exit=False)

        spec = importlib.util.spec_from_file_location("build_install", f'{self.topdir}/vendor/{self.args.product_name}/build.py')
        build_module = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(build_module)
        return build_utils.exec_doing(build_module.build_install(module_name, build_utils, f'{binpath}', f'{installpath}' ,self.args.jobs), "build.py install")

    def build_cmake(self, module_name,pkg_config_path, options, path, includes, links, deps):
        cmake_command = [
            'cmake',
            f'-S {self.topdir}/{path}',
            f'-B {self.bindir}/{path}',
            f'-DROOT_DIR={self.topdir}',
            f'-DPRODUCT_ROOT_DIR={self.topdir}',
            f'-DPRODUCT_BUILD_DIR={self.bindir}',
            f'-DPRODUCT_INSTALL_DIR={self.installdir}/{module_name}',
            f'-DCMAKE_CXX_FLAGS={self.cxxflags}',
            f'-DCMAKE_C_FLAGS={self.cflags}',
            f'-DCMAKE_EXE_LINKER_FLAGS={self.ldflags}',
            f'-DCMAKE_CROSSCOMPILING=TRUE',
            f'-DCMAKE_INSTALL_PREFIX={self.installdir}/{module_name}'
        ]
        module_paths = ";".join(self.PACKAGE_MODULE_PATH)
        if module_paths != "":
            cmake_command.append(f'-DCMAKE_MODULE_PATH={module_paths}')

        prefix_paths = ";".join(self.PACKAGE_PREFIX_PATH)
        if prefix_paths != "":
            cmake_command.append(f'-DCMAKE_PREFIX_PATH={prefix_paths}')

        for export_path in self.CONFIG_EXPORT_DIR:
            cmake_command.append(f'-D{export_path}')

        if self.toolchain != "":
            cmake_command.append(f'-DCMAKE_TOOLCHAIN_FILE={self.toolchain}')

        if self.build_mode == "Debug":
            cmake_command.append("-DCMAKE_BUILD_TYPE=DEBUG")
        else:
            cmake_command.append("-DCMAKE_BUILD_TYPE=RELEASE")
        for option in options:
            if option is not None:
                cmake_command.append(f'-D{option}')
        env = os.environ.copy()
        env['PKG_CONFIG_PATH'] = pkg_config_path
        build_utils.appemd_mention_message({'PKG_CONFIG_PATH':pkg_config_path})
        build_utils.appemd_mention_message(cmake_command)
        build_utils.exec_command(cmake_command, "cmake", env=env)

    def build_configure(self, module_name, options, path, includes, links, deps):
        if not os.path.exists(f'{self.bindir}/{path}'):
            shutil.copytree(f'{self.topdir}/{path}', f'{self.bindir}/{path}')
        configure_command = [
            f'{self.bindir}/{path}/configure',
            f'--srcdir={self.bindir}/{path}',
            f'--prefix={self.installdir}/{module_name}'
        ]

        for option in options:
            if option is not None:
                configure_command.append(option)
        print(configure_command)
        build_utils.exec_command(configure_command, "configure", f'{self.bindir}/{path}')

    def has_patch_build(self, module_name, func):
        if os.path.exists(f'{self.topdir}/vendor/{self.args.product_name}/build.py'):
            spec = importlib.util.spec_from_file_location("has_build", f'{self.topdir}/vendor/{self.args.product_name}/build.py')
            build_module = importlib.util.module_from_spec(spec)
            spec.loader.exec_module(build_module)
            return build_module.has_build(module_name, func)
        return False

    def build_make(self, module_name, options, path, include, links, deps):
        if not self.has_patch_build(module_name, 'make'):
            # make
            make_command = ['make', f'--directory={self.bindir}/{path}']
            if build_utils.exec_command(make_command, "build"):
                return True
            else:
                return False
        spec = importlib.util.spec_from_file_location("build", f'{self.topdir}/vendor/{self.args.product_name}/build.py')
        build_module = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(build_module)
        env = {}
        env['CFLAGS'] = self.cflags
        env['CXXFLAGS'] = self.cxxflags
        env['LDFLAGS'] = self.ldflags
        return build_utils.exec_doing(build_module.build(module_name, build_utils, f'{self.topdir}/{path}', f'{self.bindir}/{path}', f'{self.installdir}/{module_name}', self.args.jobs, env), "build.py")
